/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#ifndef M_VECTOR3_H
#define M_VECTOR3_H

#include <stdexcept>
#include <cmath>

namespace Engine
{

    /**
	*   Standard 3-dimensional vector.
	*/
	template<class Real>
    class  Vector3
    {
    public:
		Real x, y, z;

    public:
		/**********************************************************************************************************/
		inline Vector3():x(0),y(0),z(0)
        {
        }
		/**********************************************************************************************************/
        inline Vector3( Real fX, Real fY, Real fZ )
            : x( fX ), y( fY ), z( fZ )
        {
        }
		/**********************************************************************************************************/
        inline Vector3( Real afCoordinate[3] )
            : x( afCoordinate[0] ),
              y( afCoordinate[1] ),
              z( afCoordinate[2] )
        {
        }
		/**********************************************************************************************************/
        inline Vector3( int afCoordinate[3] )
        {
            x = (Real)afCoordinate[0];
            y = (Real)afCoordinate[1];
            z = (Real)afCoordinate[2];
        }
		/**********************************************************************************************************/
        inline Vector3( const Real* const r )
            : x( r[0] ), y( r[1] ), z( r[2] )
        {
        }
		/**********************************************************************************************************/
        inline Vector3( const Vector3& rkVector )
            : x( rkVector.x ), y( rkVector.y ), z( rkVector.z )
        {
        }
		/**********************************************************************************************************/
        inline Vector3& operator = ( const Vector3& rkVector )
        {
            x = rkVector.x;
            y = rkVector.y;
            z = rkVector.z;

            return *this;
        }
		/**********************************************************************************************************/
        inline bool operator == ( const Vector3& rkVector ) const
        {
            return ( x == rkVector.x && y == rkVector.y && z == rkVector.z );
        }
		/**********************************************************************************************************/
        inline bool operator != ( const Vector3& rkVector ) const
        {
            return ( x != rkVector.x || y != rkVector.y || z != rkVector.z );
        }
		/**********************************************************************************************************/
        inline Vector3 operator + ( const Vector3& rkVector ) const
        {
            Vector3 kSum;

            kSum.x = x + rkVector.x;
            kSum.y = y + rkVector.y;
            kSum.z = z + rkVector.z;

            return kSum;
        }
		/**********************************************************************************************************/
        inline Vector3 operator - ( const Vector3& rkVector ) const
        {
            Vector3 kDiff;

            kDiff.x = x - rkVector.x;
            kDiff.y = y - rkVector.y;
            kDiff.z = z - rkVector.z;

            return kDiff;
        }
		/**********************************************************************************************************/
        inline Vector3 operator * ( Real fScalar ) const
        {
            Vector3 kProd;

            kProd.x = fScalar*x;
            kProd.y = fScalar*y;
            kProd.z = fScalar*z;

            return kProd;
        }
		/**********************************************************************************************************/
        inline Vector3 operator * ( const Vector3& rhs) const
        {
            Vector3 kProd;

            kProd.x = rhs.x * x;
            kProd.y = rhs.y * y;
            kProd.z = rhs.z * z;

            return kProd;
        }
		/**********************************************************************************************************/
        inline Vector3 operator / ( Real fScalar ) const
        {
            if( fScalar == 0.0 )
				throw std::invalid_argument("Vector3 wrong value");

            Vector3 kDiv;

            Real fInv = 1.0f / fScalar;
            kDiv.x = x * fInv;
            kDiv.y = y * fInv;
            kDiv.z = z * fInv;

            return kDiv;
        }
		/**********************************************************************************************************/
        inline Vector3 operator / ( const Vector3& rhs) const
        {
            Vector3 kDiv;

            kDiv.x = x / rhs.x;
            kDiv.y = y / rhs.y;
            kDiv.z = z / rhs.z;

            return kDiv;
        }
		/**********************************************************************************************************/
        inline Vector3 operator - () const
        {
            Vector3 kNeg;

            kNeg.x = -x;
            kNeg.y = -y;
            kNeg.z = -z;

            return kNeg;
        }
		/**********************************************************************************************************/
        inline friend Vector3 operator * ( Real fScalar, const Vector3& rkVector )
        {
            Vector3 kProd;

            kProd.x = fScalar * rkVector.x;
            kProd.y = fScalar * rkVector.y;
            kProd.z = fScalar * rkVector.z;

            return kProd;
        }
		/**********************************************************************************************************/
        inline Vector3& operator += ( const Vector3& rkVector )
        {
            x += rkVector.x;
            y += rkVector.y;
            z += rkVector.z;

            return *this;
        }
		/**********************************************************************************************************/
        inline Vector3& operator -= ( const Vector3& rkVector )
        {
            x -= rkVector.x;
            y -= rkVector.y;
            z -= rkVector.z;

            return *this;
        }
		/**********************************************************************************************************/
        inline Vector3& operator *= ( Real fScalar )
        {
            x *= fScalar;
            y *= fScalar;
            z *= fScalar;
            return *this;
        }
		/**********************************************************************************************************/
        inline Vector3& operator *= ( const Vector3& rkVector )
        {
            x *= rkVector.x;
            y *= rkVector.y;
            z *= rkVector.z;

            return *this;
        }
		/**********************************************************************************************************/
        inline Vector3& operator /= ( Real fScalar )
        {
			if( fScalar == 0.0f )
				throw std::invalid_argument("Vector3 wrong value");


            Real fInv = 1.0f / fScalar;

            x *= fInv;
            y *= fInv;
            z *= fInv;

            return *this;
        }
		/**********************************************************************************************************/
        inline Vector3& operator /= ( const Vector3& rkVector )
        {
            x /= rkVector.x;
            y /= rkVector.y;
            z /= rkVector.z;

            return *this;
        }
		/**********************************************************************************************************/
        inline Real Length () const
        {
            return std::sqrt( x * x + y * y + z * z );
        }
		/**********************************************************************************************************/
        inline Real SquaredLength () const
        {
            return x * x + y * y + z * z;
        }
		/**********************************************************************************************************/
        inline Real DotProduct(const Vector3& vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z;
        }
		/**********************************************************************************************************/
        inline Real Normalise()
        {
            Real fLength = std::sqrt( x * x + y * y + z * z );

            // Will also work for zero-sized vectors, but will change nothing
            if ( fLength > 1e-08 )
            {
                Real fInvLength = 1.0f / fLength;
                x *= fInvLength;
                y *= fInvLength;
                z *= fInvLength;
            }

            return fLength;
        }
		/**********************************************************************************************************/
		inline Vector3 CrossProduct( const Vector3& rkVector ) const
        {
            Vector3 kCross;

            kCross.x = y * rkVector.z - z * rkVector.y;
            kCross.y = z * rkVector.x - x * rkVector.z;
            kCross.z = x * rkVector.y - y * rkVector.x;

            return kCross;
        }
		/**********************************************************************************************************/
		/**
		*   Sets this vector's components to the minimum of its own and the ones of the passed in vector.
        */
        inline void MakeFloor( const Vector3& cmp )
        {
            if( cmp.x < x ) x = cmp.x;
            if( cmp.y < y ) y = cmp.y;
            if( cmp.z < z ) z = cmp.z;
        }
		/**********************************************************************************************************/
		/**
		*   Sets this vector's components to the maximum of its own and the ones of the passed in vector.
        */
        inline void MakeCeil( const Vector3& cmp )
        {
            if( cmp.x > x ) x = cmp.x;
            if( cmp.y > y ) y = cmp.y;
            if( cmp.z > z ) z = cmp.z;
        }
		/**********************************************************************************************************/
		/**
		*   Returns true if the vector's scalar components are all greater
        *   that the ones of the vector it is compared against.
        */
        inline bool operator < ( const Vector3& rhs ) const
        {
            if( x < rhs.x && y < rhs.y && z < rhs.z )
                return true;
            return false;
        }
		/**********************************************************************************************************/
        /**
		*   Returns true if the vector's scalar components are all smaller
        *   that the ones of the vector it is compared against.
        */
        inline bool operator > ( const Vector3& rhs ) const
        {
            if( x > rhs.x && y > rhs.y && z > rhs.z )
                return true;
            return false;
        }
		/**********************************************************************************************************/
        inline bool IsZeroLength(void) const
        {
            Real sqlen = (x * x) + (y * y) + (z * z);
            return (sqlen < (1e-06 * 1e-06));

        }
		/**********************************************************************************************************/
        static const Vector3 ZERO;
        static const Vector3 UNIT_X;
        static const Vector3 UNIT_Y;
        static const Vector3 UNIT_Z;
        static const Vector3 NEGATIVE_UNIT_X;
        static const Vector3 NEGATIVE_UNIT_Y;
        static const Vector3 NEGATIVE_UNIT_Z;
        static const Vector3 UNIT_SCALE;

    };
	template<class Real>
	const Vector3<Real> Vector3<Real>::ZERO( 0, 0, 0 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::UNIT_X( 1, 0, 0 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::UNIT_Y( 0, 1, 0 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::UNIT_Z( 0, 0, 1 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::NEGATIVE_UNIT_X( -1,  0,  0 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::NEGATIVE_UNIT_Y(  0, -1,  0 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::NEGATIVE_UNIT_Z(  0,  0, -1 );
	template<class Real>
    const Vector3<Real> Vector3<Real>::UNIT_SCALE(1, 1, 1);
}

#endif
